package com.sayice.gomoku.pojo;

import com.sayice.gomoku.game.Action;
import com.sayice.gomoku.game.Game;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 游戏房间
 */
public class Room {
    /**
     * 房间号
     */
    private final int id;
    
    /**
     * 对局玩家信息 <玩家，棋子颜色(1: 黑，2: 白)>
     */
    private final Map<Player, Integer> gamerInfo = new HashMap<>(2);
    
    /**
     * 棋盘
     */
    private final int[][] board = new int[15][15];
    
    /**
     * 棋子坐标
     */
    private final List<int[]> chessCoords = new ArrayList<>();
    
    /**
     * 待下子的玩家
     */
    private Player turn;
    
    /**
     * 请求悔棋的玩家
     */
    private Player undoPlayer;
    
    /**
     * 观战玩家列表
     */
    private final List<Player> watchList = new ArrayList<>();
    
    public void setUndoPlayer(Player undoPlayer) {
        this.undoPlayer = undoPlayer;
    }
    
    public Room(int id) {
        this.id = id;
    }
    
    /**
     * 房间是否还未关闭
     *
     * @return 若房间还未关闭则返回 true，否则返回 false
     */
    public boolean isOpen() {
        return Game.roomMap.containsKey(id);
    }
    
    /**
     * 棋子是否已下满
     *
     * @return 若棋子已下满则返回 true，否则返回 false
     */
    public boolean isChessFull() {
        return chessCoords.size() == 225;
    }
    
    /**
     * 添加玩家
     *
     * @param player1 玩家1
     * @param player2 玩家2
     */
    public void addPlayers(Player player1, Player player2) throws IOException {
        Game.roomMap.put(id, this);
        Player.setFree(false, player1, player2);
        // 1 表示黑色，2 表示白色
        int color1 = 2;
        int color2 = 2;
        // 取随机数判断先后手（取到奇数则玩家 1 为先手）
        if (((int) (Math.random() * 10) & 1) == 1) {
            color1 = 1;
        } else {
            color2 = 1;
        }
        addGamerInfo(player1, color1);
        addGamerInfo(player2, color2);
    }
    
    /**
     * 添加玩家信息
     *
     * @param player 玩家
     * @param color  棋子颜色
     */
    private void addGamerInfo(Player player, int color) throws IOException {
        // 若正在匹配则先停止匹配 (stopMatch()中含判断逻辑)
        player.stopMatch();
        // 若正在观战则先退出观战 (leaveMatch()中含判断逻辑)
        player.leaveWatch();
        // leaveWatch()执行后 player.room 为 null，故其与 setRoom() 的调用顺序不能变
        player.setRoom(this);
        gamerInfo.put(player, color);
        if (color == 1) {
            // 设置待下子的玩家
            turn = player;
        }
        // 给客户端发送消息通知其进行对局初始化操作
        player.send(Action.setGameInfo, new HashMap<String, Integer>() {{
            put("roomId", id);
            put("color", color);
        }});
        // 给客户端发送观战人数
        player.send(Action.updateWatchCount, watchList.size());
    }
    
    /**
     * 处理对局玩家离开房间
     *
     * @param player 玩家
     */
    public synchronized void handleLeave(Player player) throws IOException {
        if (isOpen()) {
            close();
            Player rival = getRival(player);
            player.send(Action.lose, "失败！(中途退出)");
            rival.send(Action.win, "胜利！(对方退出游戏)");
            // 对手获胜，增加其获胜数
            rival.addWinCount();
        }
    }
    
    /**
     * 将玩家添加至观战列表
     *
     * @param player 玩家
     */
    public void addWatch(Player player) throws IOException {
        watchList.add(player);
        player.setRoom(this);
        // 给进入观战的玩家发送棋子数据
        player.send(Action.setWatcher, chessCoords);
        // 给房间里所有玩家发送观战人数
        sendToAll(Action.updateWatchCount, watchList.size());
    }
    
    /**
     * 将玩家从观战列表移除
     *
     * @param player 玩家
     */
    public void removeWatch(Player player) throws IOException {
        watchList.remove(player);
        player.setRoom(null);
        // 给客户端发送关闭房间消息
        player.send(Action.endWatch, null);
        // 给房间里所有玩家发送观战人数
        sendToAll(Action.updateWatchCount, watchList.size());
    }
    
    /**
     * 发送重绘消息
     *
     * @param player 玩家
     */
    public void sendRepaint(Player player) throws IOException {
        player.send(Action.repaint, chessCoords);
    }
    
    /**
     * 下子
     *
     * @param player 玩家
     * @param x      x坐标
     * @param y      y坐标
     * @return 下子成功后获胜返回 true，未获胜或下子失败则返回 false
     */
    public boolean playChess(Player player, int x, int y) throws IOException {
        // 能够下子的条件为轮到玩家下子且要下子的位置没有棋子
        if (turn != player) {
            // 未轮到玩家下子
            System.out.println(player.getName() + "下子失败: not turn");
            return false;
        }
        if (board[x][y] != 0) {
            // 该位置已有棋子
            return false;
        }
        // 设置下子位置棋子颜色
        board[x][y] = gamerInfo.get(player);
        // 将棋子坐标保存到坐标集合中
        chessCoords.add(new int[]{x, y});
        // 切换待下子的玩家
        turn = getRival(turn);
        // 重置请求悔棋的玩家
        undoPlayer = null;
        // 给房间里所有玩家发送下子信息 (坐标、颜色)
        sendToAll(Action.drawChess, new HashMap<String, Integer>() {{
            put("x", x);
            put("y", y);
            put("color", gamerInfo.get(player));
        }});
        // 检查下子后是否获胜
        if (checkWin(x, y)) {
            return true;
        }
        // 通知玩家下子
        turn.send(Action.setTurn, null);
        return false;
    }
    
    /**
     * 撤回棋子
     */
    public void retract() throws IOException {
        // 移除最后一个棋子并获得其坐标
        int[] coords = chessCoords.remove(chessCoords.size() - 1);
        int x = coords[0];
        int y = coords[1];
        // 颜色值为 0 表示没有棋子
        board[x][y] = 0;
        // 给房间里所有玩家发送撤回棋子信息 (坐标)
        sendToAll(Action.removeChess, new HashMap<String, Integer>() {{
            put("x", x);
            put("y", y);
        }});
        // 切换待下子的玩家
        turn = getRival(turn);
        // 通知玩家下子
        turn.send(Action.setTurn, null);
    }
    
    /**
     * 玩家是否为对局玩家
     *
     * @param player 玩家
     * @return 若玩家是对局玩家则返回 true，反之返回 false
     */
    public boolean isGamer(Player player) {
        return gamerInfo.containsKey(player);
    }
    
    /**
     * 玩家是否为观战玩家
     *
     * @param player 玩家
     * @return 若玩家是观战玩家则返回 true，反之返回 false
     */
    public boolean isWatcher(Player player) {
        return watchList.contains(player);
    }
    
    /**
     * 是否轮到玩家下子
     *
     * @param player 玩家
     * @return 轮到玩家下子返回 true，反之返回 false
     */
    public boolean isTurn(Player player) {
        return turn == player;
    }
    
    /**
     * 玩家是否请求悔棋
     *
     * @param player 玩家
     * @return 玩家请求悔棋返回 true，反之返回 false
     */
    public boolean isUndo(Player player) {
        return undoPlayer == player;
    }
    
    /**
     * 获取对局对手
     *
     * @param player 玩家
     * @return 返回玩家对手 player 对象
     */
    public Player getRival(Player player) {
        if (isGamer(player)) {
            for (Player p : gamerInfo.keySet()) {
                if (p != player) {
                    return p;
                }
            }
        }
        return null;
    }
    
    /**
     * 获取棋子总数
     *
     * @return 返回棋盘上棋子的数量
     */
    public int getChessCount() {
        return chessCoords.size();
    }
    
    /**
     * 获取最后一个棋子的坐标
     *
     * @return 返回 x、y 坐标组成的数组 [x, y]，若棋盘上没有棋子，则返回 [-1, -1]
     */
    public int[] getLastCoord() {
        if (chessCoords.size() == 0) {
            return new int[]{-1, -1};
        }
        return chessCoords.get(chessCoords.size() - 1);
    }
    
    /**
     * 给房间里所有玩家发送消息
     *
     * @param action 执行的操作
     * @param data   消息数据
     */
    private void sendToAll(Action action, Object data) throws IOException {
        // 对局玩家
        for (Player player : gamerInfo.keySet()) {
            player.send(action, data);
        }
        // 观战玩家
        for (Player player : watchList) {
            player.send(action, data);
        }
    }
    
    /**
     * 关闭房间
     */
    public void close() throws IOException {
        Game.roomMap.remove(id);
        if (id == Game.maxRoomId) {
            Game.maxRoomId--;
        }
        if (Game.roomMap.isEmpty()) {
            Game.maxRoomId = 0;
        }
        for (Player player : gamerInfo.keySet()) {
            player.setRoom(null);
            Player.setFree(true, player);
            // 增加玩家对局数
            player.addPlayCount();
        }
        for (Player player : watchList) {
            player.setRoom(null);
            player.send(Action.endWatch, "对局已结束");
        }
    }
    
    /**
     * 检查是否获胜
     *
     * @param x x坐标
     * @param y y坐标
     * @return 若在当前位置下子后获胜则返回 true，反之返回 false
     */
    public boolean checkWin(int x, int y) {
        int base = board[x][y];
        int[][] pn = {{-1, 0}, {1, 0}, {-1, 1}, {1, -1}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}};
        for (int i = 0; i < 8; ) {
            int count = 0;
            int x1 = x;
            int y1 = y;
            while (count < 4) {
                x1 += pn[i][0];
                y1 += pn[i][1];
                if (x1 < 0 || y1 < 0 || x1 >= board.length || y1 >= board.length
                        || board[x1][y1] != base) {
                    if (++i % 2 == 0) {
                        break;
                    }
                    x1 = x;
                    y1 = y;
                    continue;
                }
                count++;
            }
            if (count == 4) {
                return true;
            }
        }
        return false;
    }
}
